Flutter PopScope 返回拦截

PopScope 组件在 Flutter 中的作用是控制和管理系统的返回手势(back gesture),尤其是在应用需要特定逻辑处理返回事件时非常有用。


示例

该 Demo 来自于 PopScope class 手册,页面内包含一个 Go back' 按钮,点击按钮会弹出一个对话框(_showBackDialog),问是否退出页面。

如果点击不退出('Nevermind'),也会执行一次 Pop 操作,其作用是关闭对话框自身。

如果点击确认退出('Leave'),则执行两次 Pop 操作,第一次为退出 Dialog,第二次为退出页面。

重点是看到 TextButton 外有 PopScope 包裹。它会拦截返回事件,并再一次弹出对话框(_showBackDialog),进行二次确认。

class _PageTwoState extends State<_PageTwo> {
  void _showBackDialog() {
    showDialog<void>(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: const Text('Are you sure?'),
          content: const Text(
            'Are you sure you want to leave this page?',
          ),
          actions: <Widget>[
            TextButton(
              style: TextButton.styleFrom(
                textStyle: Theme.of(context).textTheme.labelLarge,
              ),
              child: const Text('Nevermind'),
              onPressed: () {
                Navigator.pop(context);
              },
            ),
            TextButton(
              style: TextButton.styleFrom(
                textStyle: Theme.of(context).textTheme.labelLarge,
              ),
              child: const Text('Leave'),
              onPressed: () {
                Navigator.pop(context);
                Navigator.pop(context);
              },
            ),
          ],
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text('Page Two'),
            PopScope(
              canPop: false,
              onPopInvoked: (bool didPop) {
                if (didPop) {
                  return;
                }
                _showBackDialog();
              },
              child: TextButton(
                onPressed: () {
                  _showBackDialog();
                },
                child: const Text('Go back'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

属性

PopScope 支持如下属性:

什么样的路由返回属于被拦截的呢?可以通过 Route.popDisposition 为 false 进行判断。


PopEntry 接口

PopScope 是如何实现拦截系统事件的呢?PopScope 是一个 StatefulWidget,核心实现位于状态(_PopScopeState)类中。

首先 _PopScopeState 实现了 PopEntry 接口。

在 Flutter 中,PopEntry 接口为 ModalRoute 提供了一种机制,用于监听和控制返回操作。这个接口主要关注两个方面:

该接口实现如下:

abstract class PopEntry {
  /// {@macro flutter.widgets.PopScope.onPopInvoked}
  PopInvokedCallback? get onPopInvoked;

  /// {@macro flutter.widgets.PopScope.canPop}
  ValueListenable<bool> get canPopNotifier;

  @override
  String toString() {
    return 'PopEntry canPop: ${canPopNotifier.value}, onPopInvoked: $onPopInvoked';
  }
}

其中:

  1. onPopInvoked

    • 这是一个回调函数,用于通知监听者返回操作已被触发。
    • onPopInvoked 的类型是 PopInvokedCallback,它接受一个 bool 类型的参数 didPop,指示返回操作是否成功完成。
  2. canPopNotifier

    • 这是一个 ValueListenable<bool> 类型的对象,用于控制是否允许返回操作。
    • 其值为 true 时,允许返回操作;为 false 时,则阻止返回操作。

_PopScopeState 基于 PopEntry 的实现

_PopScopeState 实现了 PopEntry 接口,使得 PopScope 组件能够具体执行对返回操作的监听和控制。

注册

_PopScopeState 的生命周期内,当依赖的环境(通常是 ModalRoute)改变时(例如,当组件被插入到树中时),它会注册自己到最近的 ModalRoute 上。这通过调用 ModalRoute.of(context).registerPopEntry(this) 实现。

具体实现如下:

@override
void didChangeDependencies() {
  super.didChangeDependencies();
  final ModalRoute<dynamic>? nextRoute = ModalRoute.of(context);
  if (nextRoute != _route) {
    _route?.unregisterPopEntry(this);
    _route = nextRoute;
    _route?.registerPopEntry(this);
  }
}

我之前有一个疑问:==PopScope 只是一个组件,它为什么能够拦截到系统返回手势?==看到这里我明白了。原来,它是通过 ModalRoute.of(context) 获取到当前页面路由,将自己作为接口实现注册进去了。

解注册

当组件被从树中移除时,它会从 ModalRoute 注销自己,确保不再接收或干预返回操作:

@override
void dispose() {
  _route?.unregisterPopEntry(this);
  canPopNotifier.dispose();
  super.dispose();
}

处理返回操作

当返回手势或命令触发时,如果 canPoptrueModalRoute 会尝试执行返回操作,并调用 onPopInvoked 回调,参数 didPoptrue

如果 canPopfalse,则不会执行返回操作,但仍会调用 onPopInvoked 回调,此时 didPopfalse,表示返回被阻止。


WillPopScope(废弃)

WillPopScope 组件已经在 v3.12.0-1.0.pre 中被废弃了。

WillPopScope 与 PopScope 的接口并不相同。

Note

WillPopScope 只拦截返回手势,与 Navigator.pop 没有关系!


WillPopScope 原理

WillPopScope 也是一个 StatefulWidget,与 PopScope 不同之处在于,WillPopScope 没有实现 PopEntry 接口,而是通过 ModalRoute 的 addScopedWillPopCallback 方法注册回调。

注册位于 didChangeDependencies,当添加到组件树、或者路哟变更时,及时与 _route 绑定,对陈旧的注册及时解绑:

@override
void didChangeDependencies() {
  super.didChangeDependencies();
  if (widget.onWillPop != null)
    _route?.removeScopedWillPopCallback(widget.onWillPop!);
  _route = ModalRoute.of(context);
  if (widget.onWillPop != null)
    _route?.addScopedWillPopCallback(widget.onWillPop!);
}

其中,onWillPop 是由外部传入的,声明如下:

/// Signature for a callback that verifies that it's OK to call [Navigator.pop].
///
/// Used by [Form.onWillPop], [ModalRoute.addScopedWillPopCallback],
/// [ModalRoute.removeScopedWillPopCallback], and [WillPopScope].
typedef WillPopCallback = Future<bool> Function();

WillPopCallback 会真正阻塞住返回过程,当异步返回 true,页面才会关闭。如果异步返回 true,则拦截住这一次返回操作。


网络资料


本文作者:Maeiee

本文链接:Flutter PopScope 返回拦截

版权声明:如无特别声明,本文即为原创文章,版权归 Maeiee 所有,未经允许不得转载!


喜欢我文章的朋友请随缘打赏,鼓励我创作更多更好的作品!